/* @flow */

/**
 * Cross-platform code generation for component v-model
 */
export function genComponentModel(
  el: ASTElement,
  value: string,
  modifiers: ?ASTModifiers
): ?boolean {
  const { number, trim } = modifiers || {};

  const baseValueExpression = "$$v";
  let valueExpression = baseValueExpression;
  if (trim) {
    valueExpression =
      `(typeof ${baseValueExpression} === 'string'` +
      `? ${baseValueExpression}.trim()` +
      `: ${baseValueExpression})`;
  }
  if (number) {
    valueExpression = `_n(${valueExpression})`;
  }
  const assignment = genAssignmentCode(value, valueExpression);

  el.model = {
    value: `(${value})`,
    expression: `"${value}"`,
    callback: `function (${baseValueExpression}) {${assignment}}`,
  };
}
/*Github:https://github.com/answershuto*/
/**
 * Cross-platform codegen helper for generating v-model value assignment code.
 */
export function genAssignmentCode(value: string, assignment: string): string {
  const modelRs = parseModel(value);
  if (modelRs.idx === null) {
    return `${value}=${assignment}`;
  } else {
    return (
      `var $$exp = ${modelRs.exp}, $$idx = ${modelRs.idx};` +
      `if (!Array.isArray($$exp)){` +
      `${value}=${assignment}}` +
      `else{$$exp.splice($$idx, 1, ${assignment})}`
    );
  }
}

/**
 * parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val)
 *
 * for loop possible cases:
 *
 * - test
 * - test[idx]
 * - test[test1[idx]]
 * - test["a"][idx]
 * - xxx.test[a[a].test1[idx]]
 * - test.xxx.a["asa"][test1[idx]]
 *
 */

let len, str, chr, index, expressionPos, expressionEndPos;

export function parseModel(val: string): Object {
  str = val;
  len = str.length;
  index = expressionPos = expressionEndPos = 0;

  if (val.indexOf("[") < 0 || val.lastIndexOf("]") < len - 1) {
    return {
      exp: val,
      idx: null,
    };
  }

  while (!eof()) {
    chr = next();
    /* istanbul ignore if */
    if (isStringStart(chr)) {
      parseString(chr);
    } else if (chr === 0x5b) {
      parseBracket(chr);
    }
  }

  return {
    exp: val.substring(0, expressionPos),
    idx: val.substring(expressionPos + 1, expressionEndPos),
  };
}

function next(): number {
  return str.charCodeAt(++index);
}

function eof(): boolean {
  return index >= len;
}

function isStringStart(chr: number): boolean {
  return chr === 0x22 || chr === 0x27;
}

function parseBracket(chr: number): void {
  let inBracket = 1;
  expressionPos = index;
  while (!eof()) {
    chr = next();
    if (isStringStart(chr)) {
      parseString(chr);
      continue;
    }
    if (chr === 0x5b) inBracket++;
    if (chr === 0x5d) inBracket--;
    if (inBracket === 0) {
      expressionEndPos = index;
      break;
    }
  }
}

function parseString(chr: number): void {
  const stringQuote = chr;
  while (!eof()) {
    chr = next();
    if (chr === stringQuote) {
      break;
    }
  }
}
